[ISSUE 57] add rocketmq api - #63
Conversation
4b07bd0 to
6b7520f
Compare
|
@linjiemiao ping, could you please help to resolve the conflict. |
Okay, I will cherry-pick your latest commit, and then I resolve the conflict. |
* feat(console): add CRD for RocketMQ Console * chore(*): add ASF header Co-authored-by: liuruiyiyang <[email protected]>
|
still need this |
|
This PR has conflicts with the base branch and cannot be merged. Please rebase or merge the base branch into your branch and resolve the conflicts: git fetch origin
git checkout issue-57-dev
git rebase origin/main
# resolve conflicts, then:
git push --force-with-leaseThis is a one-time reminder. Feel free to @mention me for a re-review after conflicts are resolved. Automated notification by github-manager-bot |
RockteMQ-AI
left a comment
There was a problem hiding this comment.
Summary
This PR modifies 10 file(s) with 1043 lines of diff. No test changes detected — consider adding test coverage.
Automated review by github-manager-bot
Additional notes (not anchored to a changed line)
- [INFO]
README.md:1— Large diff (1043 lines). Consider breaking into smaller, focused PRs for easier review. (line outside diff)
| @@ -33,7 +33,7 @@ type BrokerSpec struct { | |||
| // Add custom validation using kubebuilder tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html | |||
There was a problem hiding this comment.
No test changes detected alongside source modifications. Consider adding tests to cover the changes.
RockteMQ-AI
left a comment
There was a problem hiding this comment.
Summary
PR received and logged for review. This PR requires detailed code review by a maintainer.
Diff size: 1043 lines
Author: linjiemiao (NONE)
Automated review by RockteMQ-AI
RockteMQ-AI
left a comment
There was a problem hiding this comment.
Summary
Review of PR #63: [ISSUE 57] add rocketmq api
Findings: 13 issue(s) identified (3 critical).
CLA: unknown
Please address the inline comments above.
Automated review by github-manager-bot
| brokerSts.Namespace, "broker.Name", brokerSts.Name) | ||
| } | ||
| return reconcile.Result{Requeue: true}, nil | ||
| } else if err != nil { |
There was a problem hiding this comment.
Broker creation error is logged but not returned. The function returns reconcile.Result{Requeue: true}, nil which discards the error, defeating the controller-runtime's exponential backoff retry mechanism. The error should be returned so the framework can properly retry with backoff.
| } else { | ||
| // Resource broker will change; Only ReplicaPerGroup Size ImagePullPolicy BrokerImage can update | ||
| if !reflect.DeepEqual(brokerSts.Spec.ReplicaPerGroup, brokerFound.Spec.ReplicaPerGroup) || | ||
| !reflect.DeepEqual(brokerSts.Spec.Size, brokerFound.Spec.Size) || |
There was a problem hiding this comment.
When broker Get fails with a non-NotFound error, the error is logged but not returned. The code falls through to the status update section where brokerFound is still a zero-value &Broker{} (never populated), causing the status to be set to empty strings and potentially overwriting valid status.
| } | ||
| } | ||
| if instance.Spec.Console.ConsoleDeployment.Spec.Replicas != nil { | ||
| consoleFound := &rocketmqv1alpha1.Console{} |
There was a problem hiding this comment.
Console update calls r.client.Update(context.TODO(), consoleDep) on the desired-state object (consoleDep) instead of the existing cluster object (consoleFound). This will fail because consoleDep lacks the server-assigned ResourceVersion, and even if it succeeded it would overwrite the existing resource with a stale version.
| // Resource NameService will change; Only size nameServiceImage imagePullPolicy can update | ||
| if !reflect.DeepEqual(nameServiceSts.Spec.Size, nameServiceFound.Spec.Size) || | ||
| !reflect.DeepEqual(nameServiceSts.Spec.NameServiceImage, nameServiceFound.Spec.NameServiceImage) || | ||
| !reflect.DeepEqual(nameServiceSts.Spec.ImagePullPolicy, nameServiceFound.Spec.ImagePullPolicy) { |
There was a problem hiding this comment.
No readiness gate between NameService and Broker creation: the controller creates the Broker immediately after the NameService resource exists, without waiting for NameService pods to be Running/Ready. The README states 'the name server cluster will be created first, after all name server cluster is in running state, the operator will create the broker cluster' but the code does not enforce this ordering, which can cause brokers to fail on startup.
| env := corev1.EnvVar{ | ||
| Name: "JAVA_OPTS", | ||
| Value: fmt.Sprintf("-Drocketmq.namesrv.addr=%s -Dcom.rocketmq.sendMessageWithVIPChannel=false", share.NameServersStr), | ||
| } |
There was a problem hiding this comment.
The label mutation selectorLabels["console-cr"] = cr.Name and labels["console-cr"] = cr.Name modifies the maps from cr.Spec in place. Since maps are reference types in Go, this permanently mutates the CR's spec in memory, which can cause spurious diffs on subsequent reconcile passes and incorrect Update calls to the API server.
| Size int `json:"size"` | ||
| // NameServers defines the name service list e.g. 192.168.1.1:9876;192.168.1.2:9876 | ||
| NameServers string `json:"nameServers,omitempty"` | ||
| NameServers string `json:"nameServers"` |
There was a problem hiding this comment.
Removing omitempty from the nameServers JSON tag (json:"nameServers,omitempty" -> json:"nameServers") is a breaking API change. Existing Broker resources serialized without nameServers will now fail deserialization or validation. This breaks backward compatibility for users of the standalone Broker CRD.
| spec: | ||
| properties: | ||
| broker: | ||
| description: Broker defines rocketmq broker spec info |
There was a problem hiding this comment.
The CRD validation schema defines broker, nameService, and console as type: object with no sub-properties. This provides zero validation for nested fields, meaning any malformed spec will be accepted by the API server. At minimum, required sub-fields should be enumerated, or the full nested schemas from the Broker/NameService/Console CRDs should be inlined.
| err = r.client.Get(context.TODO(), types.NamespacedName{Name: consoleDep.Name, Namespace: consoleDep.Namespace}, consoleFound) | ||
| if err != nil && errors.IsNotFound(err) { | ||
| err = r.client.Create(context.TODO(), consoleDep) | ||
| if err != nil { |
There was a problem hiding this comment.
Console creation error is logged but not returned (reqLogger.Error(err, ...) without return reconcile.Result{}, err). This means console creation failures are silently swallowed and won't trigger proper retry with backoff.
| // Note: | ||
| // The Controller will requeue the Request to be processed again if the returned error is non-nil or | ||
| // Result.Requeue is true, otherwise upon completion it will remove the work from the queue. | ||
| func (r *ReconcileRocketmq) Reconcile(request reconcile.Request) (reconcile.Result, error) { |
There was a problem hiding this comment.
No unit or integration tests are included for the new Rocketmq controller, which orchestrates three sub-resources (NameService, Broker, Console) with non-trivial reconciliation logic including conditional creation, selective field updates, and status management. This is a significant gap in test coverage for a new controller.
| spec: | ||
| type: NodePort | ||
| selector: | ||
| name_service_cr: ${rocketmq-name}-name-service |
There was a problem hiding this comment.
The selector name_service_cr: ${rocketmq-name}-name-service uses a shell variable placeholder ${rocketmq-name} that is not substituted by kubectl. Users must manually replace this value, but this is not documented and will silently result in a Service with no matching pods if applied as-is.
RockteMQ-AI
left a comment
There was a problem hiding this comment.
Summary
The new unified Rocketmq CRD is a reasonable architectural direction, but the controller has several critical bugs — missing error returns, no NameService readiness gating before broker creation, a broken console update path, and stale status references — that will cause failures on first deploy and updates.
Findings
- [CRITICAL]
pkg/controller/rocketmq/rocketmq_controller.go:152— Broker creation error is logged but not returned. Whenr.client.Createfails, the code falls through to thereturn reconcile.Result{Requeue: true}, nilbelow, silently swallowing the error. This shouldreturn reconcile.Result{}, erron create failure, consistent with the NameService create path above. - [CRITICAL]
pkg/controller/rocketmq/rocketmq_controller.go:120— No NameService readiness check before broker creation. The NameService CR is created (line 118) and the broker is created immediately after (line 148) in the same reconcile pass. The README states 'the name server cluster will be created first, after all name server cluster is in running state, the operator will create the broker cluster', but this code does not wait for NameService pods to become Ready. The broker'snameServersendpoint will be unreachable on first deploy. Add a check that all NameService pods are Running/Ready before proceeding to broker creation — returnreconcile.Result{RequeueAfter: ...}if they aren't. - [CRITICAL]
pkg/controller/rocketmq/rocketmq_controller.go:185— Console update callsr.client.Update(context.TODO(), consoleDep)with the freshly constructed desired object instead of the modifiedconsoleFound. This means the update sends an object without a ResourceVersion, which the API server will reject. Should modifyconsoleFound.Specand updateconsoleFound, matching the pattern used for NameService and Broker updates above. - [CRITICAL]
pkg/controller/rocketmq/rocketmq_controller.go:198— Status update referencesbrokerFound.ObjectMeta.NameandnameServiceFound.ObjectMeta.Name, but when the broker or NameService was just created in this reconcile pass,brokerFound/nameServiceFoundwere never populated from the API server — they remain zero-value structs with emptyObjectMeta.Name. This writes empty strings to status. Use the names from the constructed objects (brokerSts.Name,nameServiceSts.Name) instead. - [WARNING]
pkg/apis/rocketmq/v1alpha1/broker_types.go:36— Removingomitemptyfrom theNameServersjson tag makes this field required in serialization. This is a breaking change for existingBrokerCRs that relied on the field being optional. Any existing Broker resource withoutnameServersset will now fail validation on update. Keepomitemptyor add a migration note. - [WARNING]
pkg/controller/console/console_controller.go:185— The IIFE directly mutatescr.Spec.ConsoleDeployment.Spec.Selector.MatchLabelsandcr.Spec.ConsoleDeployment.Spec.Template.Labels— these are references to the CR's underlying maps, not copies. Adding theconsole-crkey modifies the in-memory CR object, which can cause unexpected behavior if the object is read from cache later. Copy the maps before mutating. - [WARNING]
pkg/controller/console/console_controller.go:223—SetControllerReferenceis called here insidenewDeploymentForCR, and also in the caller at line 139 of the Reconcile function. The second call will overwrite the first (or error if the owner is already set to a different UID). Remove one of the duplicate calls. - [WARNING]
pkg/controller/rocketmq/rocketmq_controller.go:155— After a successful broker creation, the code unconditionally returnsreconcile.Result{Requeue: true}, nilwithout waiting for the broker's sub-controller to reconcile. This means the broker update block (line 160) and console block (line 176) are unreachable on the creation pass. Consider falling through or requeueing with a delay to allow the broker controller to act. - [WARNING]
deploy/crds/rocketmq_v1alpha1_rocketmq_crd.yaml:37— Thebroker,nameService, andconsolefields underspecare declared astype: objectwith no nested properties or validation. This means any arbitrary YAML is accepted under these fields with no schema enforcement. Users will get no validation feedback for misspelled or missing required fields. Either add property-level validation or usex-kubernetes-preserve-unknown-fieldsexplicitly to document the intent. - [INFO]
pkg/apis/rocketmq/v1alpha1/rocketmq_types.go:43—RocketmqStatusfieldsBrokerandNameServiceare just bare strings with noomitempty. The CRD YAML marks them asrequiredin status. On initial creation before the first status update, these will be empty strings, which may confuse consumers expecting meaningful values. Consider addingomitemptyand making them optional in the CRD schema. - [INFO]
pkg/controller/rocketmq/rocketmq_controller.go:97— No tests are provided for this new controller. Given the reconciliation complexity (orchestrating three sub-resources with ordering constraints), at minimum there should be unit tests fornameServiceForRocketmq,brokerForRocketmq, andconsoleForRocketmqbuilders, and integration tests for the reconcile loop covering create, update, and error paths. - [INFO]
deploy/crds/rocketmq_v1alpha1_rocketmq_crd.yaml:1— CRD usesapiextensions.k8s.io/v1beta1which has been deprecated since Kubernetes 1.16 and removed in 1.22. Consider migrating toapiextensions.k8s.io/v1for forward compatibility.
Automated review by github-manager-bot
| err = r.client.Create(context.TODO(), brokerSts) | ||
| if err != nil { | ||
| reqLogger.Error(err, "Failed to create new broker of rocketmq", "broker.namespace", | ||
| brokerSts.Namespace, "broker.Name", brokerSts.Name) |
There was a problem hiding this comment.
Broker creation error is logged but not returned. When r.client.Create fails, the code falls through to the return reconcile.Result{Requeue: true}, nil below, silently swallowing the error. This should return reconcile.Result{}, err on create failure, consistent with the NameService create path above.
| err = r.client.Create(context.TODO(), nameServiceSts) | ||
| if err != nil { | ||
| reqLogger.Error(err, "Failed to create new nameService of rocketmq", "nameservice.namespace", | ||
| nameServiceSts.Namespace, "nameservice.Name", nameServiceSts.Name) |
There was a problem hiding this comment.
No NameService readiness check before broker creation. The NameService CR is created (line 118) and the broker is created immediately after (line 148) in the same reconcile pass. The README states 'the name server cluster will be created first, after all name server cluster is in running state, the operator will create the broker cluster', but this code does not wait for NameService pods to become Ready. The broker's nameServers endpoint will be unreachable on first deploy. Add a check that all NameService pods are Running/Ready before proceeding to broker creation — return reconcile.Result{RequeueAfter: ...} if they aren't.
| reqLogger.Error(err, "Failed to create new console of rocketmq") | ||
| } | ||
| } else if err != nil { | ||
| reqLogger.Error(err, "Failed to get rocketmq console.") |
There was a problem hiding this comment.
Console update calls r.client.Update(context.TODO(), consoleDep) with the freshly constructed desired object instead of the modified consoleFound. This means the update sends an object without a ResourceVersion, which the API server will reject. Should modify consoleFound.Spec and update consoleFound, matching the pattern used for NameService and Broker updates above.
| // update instance status | ||
| if !reflect.DeepEqual(instance.Status.Broker, brokerFound.ObjectMeta.Name) || | ||
| !reflect.DeepEqual(instance.Status.NameService, nameServiceFound.ObjectMeta.Name) { | ||
| instance.Status.Broker = brokerFound.ObjectMeta.Name |
There was a problem hiding this comment.
Status update references brokerFound.ObjectMeta.Name and nameServiceFound.ObjectMeta.Name, but when the broker or NameService was just created in this reconcile pass, brokerFound/nameServiceFound were never populated from the API server — they remain zero-value structs with empty ObjectMeta.Name. This writes empty strings to status. Use the names from the constructed objects (brokerSts.Name, nameServiceSts.Name) instead.
| Size int `json:"size"` | ||
| // NameServers defines the name service list e.g. 192.168.1.1:9876;192.168.1.2:9876 | ||
| NameServers string `json:"nameServers,omitempty"` | ||
| NameServers string `json:"nameServers"` |
There was a problem hiding this comment.
Removing omitempty from the NameServers json tag makes this field required in serialization. This is a breaking change for existing Broker CRs that relied on the field being optional. Any existing Broker resource without nameServers set will now fail validation on update. Keep omitempty or add a migration note.
| brokerSts.Namespace, "broker.Name", brokerSts.Name) | ||
| } | ||
| return reconcile.Result{Requeue: true}, nil | ||
| } else if err != nil { |
There was a problem hiding this comment.
After a successful broker creation, the code unconditionally returns reconcile.Result{Requeue: true}, nil without waiting for the broker's sub-controller to reconcile. This means the broker update block (line 160) and console block (line 176) are unreachable on the creation pass. Consider falling through or requeueing with a delay to allow the broker controller to act.
| type: object | ||
| nameService: | ||
| description: NameService defines rocketmq name service spec info | ||
| type: object |
There was a problem hiding this comment.
The broker, nameService, and console fields under spec are declared as type: object with no nested properties or validation. This means any arbitrary YAML is accepted under these fields with no schema enforcement. Users will get no validation feedback for misspelled or missing required fields. Either add property-level validation or use x-kubernetes-preserve-unknown-fields explicitly to document the intent.
| // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster | ||
| // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file | ||
| // Add custom validation using kubebuilder tags: https://book-v1.book.kubebuilder.io/beyond_basics/generating_crd.html | ||
| Broker string `json:"broker"` |
There was a problem hiding this comment.
RocketmqStatus fields Broker and NameService are just bare strings with no omitempty. The CRD YAML marks them as required in status. On initial creation before the first status update, these will be empty strings, which may confuse consumers expecting meaningful values. Consider adding omitempty and making them optional in the CRD schema.
| // Result.Requeue is true, otherwise upon completion it will remove the work from the queue. | ||
| func (r *ReconcileRocketmq) Reconcile(request reconcile.Request) (reconcile.Result, error) { | ||
| reqLogger := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) | ||
| reqLogger.Info("Reconciling Rocketmq") |
There was a problem hiding this comment.
No tests are provided for this new controller. Given the reconciliation complexity (orchestrating three sub-resources with ordering constraints), at minimum there should be unit tests for nameServiceForRocketmq, brokerForRocketmq, and consoleForRocketmq builders, and integration tests for the reconcile loop covering create, update, and error paths.
| @@ -0,0 +1,65 @@ | |||
| apiVersion: apiextensions.k8s.io/v1beta1 | |||
There was a problem hiding this comment.
CRD uses apiextensions.k8s.io/v1beta1 which has been deprecated since Kubernetes 1.16 and removed in 1.22. Consider migrating to apiextensions.k8s.io/v1 for forward compatibility.
add rocketmq api